module.exports = (io, app) => {
    const online = new Map();
    const jwt = require('jsonwebtoken');
    const User = require('../../models/User');
    const PM = require('../../models/PrivateMessage');

    io.on('error', function() {
        console.log('error');
    });

    io.on('connection', async function(socket) {
        let token = socket.handshake.query.token;
        if (!token) {
            io.in(socket.id).disconnectSockets();
            return
        }
        let id = null;
        //验证token
        try {
            let data = jwt.verify(token, app.get('secret'));
            id = data.id;
        } catch (e) {
            if (e.message === 'jwt malformed') {
                io.in(socket.id).disconnectSockets();
                return
            }
        }
        let userID = id;
        if (online.has(userID)) {
            //踢出前一个登录环境
            socket.to(online.get(userID)).emit('getOut');
            io.in(online.get(userID)).disconnectSockets();
        }
        //把socket存到hash表里
        online.set(userID, socket.id);

        let user = await User.findById(userID)
            .populate('friends')
            .populate('friendReq')
            .populate('currentMessages.opposite');
        for (let i = 0; i < user.currentMessages.length; i++) {
            let message = await PM.findById(user.currentMessages[i].content);
            message.mList = message.mList.slice(-15);
            user.currentMessages[i].content = message;
        }

        //在线好友人数
        let count = 0;
        for (let item of user.friends) {
            let itemID = item._id.toString();
            if (online.has(itemID)) {
                count++;
            }
            //顺便广播在线好友
            let oppositeSocket = online.get(itemID);
            if (oppositeSocket) {
                socket.to(oppositeSocket).emit('updateOnline', {
                    num: 1,
                });
            }
        }
        /*
        * @user: 用户
        * @online: 在线人数
        * */
        socket.emit('initUser', {
            user: user,
            online: count,
        });

        socket.on("disconnect", async function () {
            online.delete(userID);
            user = await User.findById(userID);
            for (let item of user.friends) {
                let oppositeSocket = online.get(item.toString());
                if (oppositeSocket) {
                    socket.to(oppositeSocket).emit('updateOnline', {
                        num: -1,
                    });
                }
            }
        });

        socket.on('getOnline', function () {
            let count = 0;
            for (let item of user.friends) {
                if (online.has(item.toString())) {
                    count++;
                }
            }
            socket.emit('updateOnline', {
                num: count
            })
        })

        socket.on('getTargetOnline', function (targetID) {
            if (online.has(targetID)) {
                socket.emit('updateOnline', {
                    num: 1
                })
            }
        })

        socket.on('friendReq', async function (targetID) {
            const target = await User.findById(targetID);
            target.friendReq.unshift(userID);
            for (let i = 1; i < target.friendReq.length; i++) {
                if (target.friendReq[i].toString() === userID) {
                    target.friendReq.splice(i, 1);
                    break
                }
            }
            target.save();

            const targetSocket = online.get(targetID);
            if (targetSocket) {
                const user = await User.findById(userID);
                socket.to(targetSocket)
                    .emit('updateFriendReq', {
                        target: user,
                        add: true
                    });
            }
        })

        socket.on('agreeReq', async function (targetID) {
            const user = await User.findById(userID);
            const target = await User.findById(targetID);

            await clear(user, targetID);
            await clear(target, userID);

            const targetSocket = online.get(targetID);
            if (targetSocket) {
                socket.to(targetSocket)
                    .emit('updateFriList', user);
            }
            socket.emit('updateFriList', target);

            async function clear(target, userID) {
                for (let i = 0; i < target.friendReq.length; i++) {
                    if (target.friendReq[i].toString() === userID) {
                        target.friendReq.splice(i, 1);
                        break
                    }
                }
                target.friends.push(userID);
                target.save();
            }
        })

        socket.on('rejectReq', async function (targetID) {
            const user = await User.findById(userID);
            for (let i = 0; i < user.friendReq.length; i++) {
                if (user.friendReq[i].toString() === targetID) {
                    user.friendReq.splice(i, 1);
                    break
                }
            }
            user.save();
            const target = await User.findById(targetID);
            socket.emit('updateFriendReq', {
                target: target,
                add: false
            });
        })

        socket.on('clearUnread', async function (mID) {
            const user = await User.findById(userID);
            for (let item of user.currentMessages) {
                if (item.content.toString() === mID) {
                    item.unread = 0;
                    user.save();
                    break;
                }
            }
        })

        socket.on('loadMoreMessage', async function (data) {
            const {limit, mID} = data;
            const message = await PM.findById(mID);
            const moreMessage = message.mList.slice(-(limit + 15), -limit);
            socket.emit('addMoreMessage', {
                moreMessage: moreMessage,
                mID: mID,
            });
        })

        socket.on('postMessage', async function (data) {
            const {text, preText, textType, targetID, messageID} = data;

            let user = await User.findById(userID);
            let target = await User.findById(targetID);

            let message;
            if (messageID !== '') {
                message = await PM.findById(messageID);

                //把需要更新的会话排到第一位
                let userCut = cut(user, messageID)[0];
                user.currentMessages.unshift(userCut);
                user.save()

                let targetCut = cut(target, messageID)[0];
                //顺便增加未读文本数目
                targetCut.unread += 1;
                target.currentMessages.unshift(targetCut);
                target.save();

                function cut(user, mID) {
                    for (let i = 0; i < user.currentMessages.length; i++) {
                        if (user.currentMessages[i].content.toString() === mID) {
                            return user.currentMessages.splice(i, 1);
                        }
                    }
                }
            } else {
                message = await PM.create({
                    users: [userID, targetID],
                    mList: []
                })
                user.currentMessages.unshift({
                    opposite: targetID,
                    content: message._id
                })
                target.currentMessages.unshift({
                    opposite: userID,
                    content: message._id,
                    unread: 1
                })
                user.save();
                target.save();
            }

            message.mList.push({
                poster: user.username,
                text: text,
                preText: preText,
                textType: textType,
                time: new Date()
            })
            message.listLength += 1;
            message.save();

            let post = message.mList[message.mList.length - 1];
            findTargetAndEmit(targetID, 'updateMessage', {
                mID: message._id,
                oppositeID: userID,
                post: post,
                unreadAdd: 1,
            })
            socket.emit('updateMessage', {
                mID: message._id,
                oppositeID: targetID,
                post: post,
                unreadAdd: 0,
            })
        });

        socket.on('delChat', async function (req) {
            let user = await User.findById(userID);
            let opposite = await User.findById(req.oppositeID);
            findAndDel(user, req.oppositeID);
            findAndDel(opposite, userID);
            await PM.findByIdAndDelete(req.mID);
            findTargetAndEmit(req.oppositeID, 'delChatAccess', req.mID)
            socket.emit('delChatAccess', req.mID);

            function findAndDel(user, oppositeID) {
                for (let i = 0; i < user.currentMessages.length; i++) {
                    if (user.currentMessages[i].opposite.toString() === oppositeID) {
                        user.currentMessages.splice(i, 1);
                        user.save();
                        break
                    }
                }
            }
        })

        socket.on('avatarChange', async function (post) {
            const user = await User.findById(userID);
            user.profile = post;
            user.save();

            let data = {
                data: post,
                target: userID,
            }
            socket.emit('updateAvatar', data)
            for (let item of user.friends) {
                let targetSocket = online.get(item.toString());
                if (targetSocket) {
                    socket.to(targetSocket).emit('updateAvatar', data)
                }
            }
        })

        function findTargetAndEmit(targetID, method, post) {
            const targetSocket = online.get(targetID);
            if (targetSocket) {
                socket.to(targetSocket).emit(method, post);
            }
        }
    });
}